/**
 * 文件验证工具
 * EndoSight-UC 医疗AI系统
 *
 * 提供全面的文件安全验证，特别是医疗图像文件
 */

import crypto from 'crypto';
import fs from 'fs';
import path from 'path';
import { createError } from './errorTypes.js';

// 文件类型魔数定义
const FILE_SIGNATURES = {
  // JPEG 图像
  'image/jpeg': [
    [0xFF, 0xD8, 0xFF, 0xE0], // JPEG with JFIF
    [0xFF, 0xD8, 0xFF, 0xE1], // JPEG with EXIF
    [0xFF, 0xD8, 0xFF, 0xE8], // JPEG with SPIFF
    [0xFF, 0xD8, 0xFF, 0xFE]  // JPEG
  ],

  // PNG 图像
  'image/png': [
    [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]
  ],

  // BMP 图像
  'image/bmp': [
    [0x42, 0x4D] // BM
  ],

  // DICOM 医学图像 (简化检测)
  'application/dicom': [
    [0x44, 0x49, 0x43, 0x4D], // DICM
    [0x02, 0x00, 0x00, 0x00], // DICOM implicit VR little endian
    [0x4C, 0x00, 0x00, 0x00]  // DICOM explicit VR little endian
  ]
};

// 医疗图像文件扩展名
const MEDICAL_IMAGE_EXTENSIONS = [
  '.jpg', '.jpeg', '.png', '.bmp', '.dcm', '.dicom', '.tiff', '.tif'
];

// 危险文件扩展名黑名单
const DANGEROUS_EXTENSIONS = [
  '.exe', '.bat', '.cmd', '.com', '.pif', '.scr', '.vbs', '.js', '.jar',
  '.php', '.asp', '.aspx', '.jsp', '.sh', '.ps1', '.py', '.rb', '.pl',
  '.msi', '.deb', '.rpm', '.dmg', '.app', '.appimage', '.bin',
  '.zip', '.rar', '.7z', '.tar', '.gz', '.bz2', '.xz'
];

// 允许的MIME类型
const ALLOWED_MIME_TYPES = [
  'image/jpeg',
  'image/png',
  'image/bmp',
  'application/dicom'
];

// 最大文件大小 (10MB)
const MAX_FILE_SIZE = 10 * 1024 * 1024;

// 最小图像尺寸
const MIN_IMAGE_DIMENSIONS = { width: 100, height: 100 };

// 最大图像尺寸
const MAX_IMAGE_DIMENSIONS = { width: 4096, height: 4096 };

/**
 * 验证文件安全性
 * @param {Object} file - 上传的文件对象
 * @param {Object} options - 验证选项
 * @returns {Promise<Object>} 验证结果
 */
export const validateFile = async (file, options = {}) => {
  const {
    maxSize = MAX_FILE_SIZE,
    allowedTypes = ALLOWED_MIME_TYPES,
    requireMedicalImage = true
  } = options;

  try {
    // 1. 基础验证
    await validateBasicProperties(file, maxSize);

    // 2. 扩展名验证
    validateFileExtension(file);

    // 3. MIME类型验证
    validateMimeType(file, allowedTypes);

    // 4. 文件头魔数验证
    await validateFileSignature(file);

    // 5. 内容安全扫描
    await scanFileContent(file);

    // 6. 如果是图像，验证图像属性
    if (file.mimetype.startsWith('image/')) {
      await validateImageProperties(file);
    }

    // 7. 医疗图像特定验证
    if (requireMedicalImage) {
      validateMedicalImageRequirements(file);
    }

    // 8. 生成文件哈希
    const fileHash = await generateFileHash(file);

    return {
      valid: true,
      fileHash,
      metadata: {
        originalName: file.originalname,
        mimetype: file.mimetype,
        size: file.size,
        extension: path.extname(file.originalname).toLowerCase()
      }
    };

  } catch (error) {
    return {
      valid: false,
      error: error.message,
      errorCode: error.code || 'FILE_VALIDATION_ERROR'
    };
  }
};

/**
 * 基础属性验证
 */
const validateBasicProperties = async (file, maxSize) => {
  if (!file) {
    throw createError.fileValidation('文件对象不能为空');
  }

  if (!file.originalname || typeof file.originalname !== 'string') {
    throw createError.fileValidation('文件名无效');
  }

  if (!file.mimetype || typeof file.mimetype !== 'string') {
    throw createError.fileValidation('MIME类型无效');
  }

  if (!file.buffer || !Buffer.isBuffer(file.buffer)) {
    throw createError.fileValidation('文件内容无效');
  }

  if (file.size <= 0) {
    throw createError.fileValidation('文件大小不能为零');
  }

  if (file.size > maxSize) {
    throw createError.fileValidation(
      `文件大小超过限制 (${Math.round(maxSize / 1024 / 1024)}MB)`,
      {
        actualSize: file.size,
        maxSize,
        actualSizeMB: Math.round(file.size / 1024 / 1024)
      }
    );
  }
};

/**
 * 文件扩展名验证
 */
const validateFileExtension = (file) => {
  const extension = path.extname(file.originalname).toLowerCase();

  // 检查是否为危险扩展名
  if (DANGEROUS_EXTENSIONS.includes(extension)) {
    throw createError.maliciousFile(
      `不允许的文件扩展名: ${extension}`,
      { extension, filename: file.originalname }
    );
  }

  // 检查是否为医疗图像扩展名
  if (!MEDICAL_IMAGE_EXTENSIONS.includes(extension)) {
    throw createError.medicalImageValidation(
      `不是有效的医疗图像文件扩展名: ${extension}`,
      { extension, allowedExtensions: MEDICAL_IMAGE_EXTENSIONS }
    );
  }
};

/**
 * MIME类型验证
 */
const validateMimeType = (file, allowedTypes) => {
  if (!allowedTypes.includes(file.mimetype)) {
    throw createError.medicalImageValidation(
      `不支持的MIME类型: ${file.mimetype}`,
      {
        mimetype: file.mimetype,
        allowedTypes,
        filename: file.originalname
      }
    );
  }
};

/**
 * 文件头魔数验证
 */
const validateFileSignature = async (file) => {
  const buffer = file.buffer;
  const expectedSignatures = FILE_SIGNATURES[file.mimetype];

  if (!expectedSignatures) {
    throw createError.fileValidation(
      `未知的文件类型: ${file.mimetype}`
    );
  }

  const isValidSignature = expectedSignatures.some(signature => {
    return signature.every((byte, index) => buffer[index] === byte);
  });

  if (!isValidSignature) {
    throw createError.maliciousFile(
      '文件头与扩展名不匹配，可能是恶意文件',
      {
        mimetype: file.mimetype,
        filename: file.originalname,
        actualHeader: Array.from(buffer.slice(0, 16))
      }
    );
  }
};

/**
 * 文件内容安全扫描
 */
const scanFileContent = async (file) => {
  const buffer = file.buffer;

  // 检查可疑字符串
  const suspiciousPatterns = [
    /<script/i,
    /javascript:/i,
    /vbscript:/i,
    /onload=/i,
    /onerror=/i,
    /eval\(/i,
    /document\./i,
    /window\./i
  ];

  const content = buffer.toString('utf8', 0, Math.min(1024, buffer.length));

  for (const pattern of suspiciousPatterns) {
    if (pattern.test(content)) {
      throw createError.maliciousFile(
        '文件包含可疑代码',
        { pattern: pattern.source, filename: file.originalname }
      );
    }
  }

  // 检查是否包含PE文件头 (可执行文件)
  const peSignature = [0x4D, 0x5A]; // MZ
  if (buffer[0] === peSignature[0] && buffer[1] === peSignature[1]) {
    throw createError.maliciousFile(
      '检测到可执行文件头',
      { filename: file.originalname }
    );
  }
};

/**
 * 图像属性验证
 */
const validateImageProperties = async (file) => {
  try {
    // 这里可以集成 sharp 或 jimp 库进行图像处理验证
    // 为了简单起见，我们进行基础验证

    const buffer = file.buffer;

    // 对于JPEG，验证基本结构
    if (file.mimetype === 'image/jpeg') {
      await validateJPEGStructure(buffer);
    }

    // 对于PNG，验证基本结构
    else if (file.mimetype === 'image/png') {
      await validatePNGStructure(buffer);
    }

    // 对于BMP，验证基本结构
    else if (file.mimetype === 'image/bmp') {
      await validateBMPStructure(buffer);
    }

  } catch (error) {
    if (error.code === 'MEDICAL_IMAGE_VALIDATION_ERROR') {
      throw error;
    }
    throw createError.medicalImageValidation(
      `图像文件结构无效: ${error.message}`,
      { filename: file.originalname, originalError: error.message }
    );
  }
};

/**
 * JPEG结构验证
 */
const validateJPEGStructure = async (buffer) => {
  if (buffer.length < 4) {
    throw new Error('JPEG文件太小');
  }

  // 验证JPEG标识符
  if (buffer[0] !== 0xFF || buffer[1] !== 0xD8) {
    throw new Error('无效的JPEG文件头');
  }

  // 查找JPEG结束标记
  let hasEndMarker = false;
  for (let i = buffer.length - 2; i >= 0; i--) {
    if (buffer[i] === 0xFF && buffer[i + 1] === 0xD9) {
      hasEndMarker = true;
      break;
    }
  }

  if (!hasEndMarker) {
    throw new Error('JPEG文件缺少结束标记');
  }
};

/**
 * PNG结构验证
 */
const validatePNGStructure = async (buffer) => {
  if (buffer.length < 8) {
    throw new Error('PNG文件太小');
  }

  // PNG文件头
  const pngHeader = Buffer.from([0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]);
  if (!buffer.slice(0, 8).equals(pngHeader)) {
    throw new Error('无效的PNG文件头');
  }

  // 验证IHDR块存在
  if (buffer.length < 33) {
    throw new Error('PNG文件缺少必需的IHDR块');
  }

  // 检查PNG结束标记
  const endChunk = buffer.slice(-8);
  const iendSignature = Buffer.from([0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44]);
  if (!endChunk.equals(iendSignature)) {
    throw new Error('PNG文件缺少IEND块');
  }
};

/**
 * BMP结构验证
 */
const validateBMPStructure = async (buffer) => {
  if (buffer.length < 54) {
    throw new Error('BMP文件太小');
  }

  // BMP文件头
  if (buffer[0] !== 0x42 || buffer[1] !== 0x4D) { // 'BM'
    throw new Error('无效的BMP文件头');
  }

  // 验证文件大小
  const fileSize = buffer.readUInt32LE(2);
  if (fileSize !== buffer.length) {
    throw new Error('BMP文件大小不匹配');
  }
};

/**
 * 医疗图像特定验证
 */
const validateMedicalImageRequirements = (file) => {
  // 检查文件名是否包含医疗相关信息
  const medicalKeywords = [
    'endo', 'colon', 'intestinal', 'medical', 'patient', 'study',
    'endoscopy', '内窥', '结肠', '肠道', '医学', '患者'
  ];

  const filename = file.originalname.toLowerCase();
  const hasMedicalKeyword = medicalKeywords.some(keyword =>
    filename.includes(keyword)
  );

  if (!hasMedicalKeyword) {
    // 警告但不阻止，因为有些文件可能不包含这些关键词
    console.warn(`文件名可能不包含医疗相关信息: ${file.originalname}`);
  }

  // DICOM文件特殊验证
  if (file.mimetype === 'application/dicom') {
    validateDICOMRequirements(file);
  }
};

/**
 * DICOM文件特定验证
 */
const validateDICOMRequirements = (file) => {
  const buffer = file.buffer;

  // 检查DICOM文件最小大小
  if (buffer.length < 132) {
    throw createError.medicalImageValidation(
      'DICOM文件太小',
      { filename: file.originalname }
    );
  }

  // 检查DICOM前缀
  const dicomPrefix = buffer.slice(128, 132);
  const expectedPrefix = Buffer.from('DICM');

  if (!dicomPrefix.equals(expectedPrefix)) {
    // 某些DICOM文件可能没有标准前缀，但这不一定意味着文件无效
    console.warn('DICOM文件缺少标准前缀，但仍会尝试处理');
  }
};

/**
 * 生成文件哈希值
 */
const generateFileHash = async (file) => {
  return crypto.createHash('sha256').update(file.buffer).digest('hex');
};

/**
 * 清理和标准化文件名
 */
export const sanitizeFilename = (filename) => {
  if (!filename || typeof filename !== 'string') {
    return 'unnamed_file';
  }

  // 移除路径分隔符和其他危险字符
  const sanitized = filename
    .replace(/[\\\/]/g, '_')
    .replace(/[:*?"<>|]/g, '')
    .replace(/\s+/g, '_')
    .toLowerCase();

  // 确保文件名不为空
  return sanitized || 'unnamed_file';
};

/**
 * 生成安全的文件名
 */
export const generateSecureFilename = (originalName, fileHash) => {
  const timestamp = Date.now();
  const extension = path.extname(originalName).toLowerCase();
  const baseName = path.basename(originalName, extension);
  const sanitizedBaseName = sanitizeFilename(baseName);

  return `${sanitizedBaseName}_${fileHash.slice(0, 8)}_${timestamp}${extension}`;
};

export default {
  validateFile,
  sanitizeFilename,
  generateSecureFilename,
  FILE_SIGNATURES,
  MEDICAL_IMAGE_EXTENSIONS,
  ALLOWED_MIME_TYPES
};